package de.jonasjelten.jcalc.math;

import de.jonasjelten.jcalc.exception.JMathematicalException;
import de.jonasjelten.jcalc.exception.JGCDException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Random;

/**
 *
 * @author michael & Jonas
 */
public class JMathOps {

    /**
     * Berechnung der Kreiszahl PI zu einer beliebigen Genauigkeit.
     * Dein PC muss nur genügend RAM und Laufzeit haben, alles ist dann möglich :-D
     * 
     * @param scale anzahl gewünschter korrekter pi-stellen
     * @return die Zahl PI auf <code>scale</code> stellen
     */
    public static BigDecimal getPi(int scale) {
        int limit = scale;
        int intermediateResultAccuracy = scale + 5 + (int) Math.log10(limit);
        BigDecimal accuracy = new BigDecimal(BigInteger.ONE, scale + 5);

        BigDecimal result = BigDecimal.ZERO;
        BigDecimal preScalerFactor = new BigDecimal("-0.0009765625");
        BigDecimal preScaler = BigDecimal.ONE;
        for (int i = 0;; i++) {
            BigDecimal summand =
                    preScaler.multiply(
                    BigDecimal.valueOf(-1).divide(new BigDecimal(BigInteger.valueOf(8).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(2))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(+4).divide(new BigDecimal(BigInteger.valueOf(10).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(1))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(-1).divide(new BigDecimal(BigInteger.valueOf(10).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(3))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(-1).divide(new BigDecimal(BigInteger.valueOf(160).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(80))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(-1).divide(new BigDecimal(BigInteger.valueOf(160).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(112))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(-1).divide(new BigDecimal(BigInteger.valueOf(256).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(192))), intermediateResultAccuracy, RoundingMode.HALF_UP).add(
                    BigDecimal.valueOf(+1).divide(new BigDecimal(BigInteger.valueOf(640).multiply(BigInteger.valueOf(i)).add(BigInteger.valueOf(576))), intermediateResultAccuracy, RoundingMode.HALF_UP))))))));
            if (summand.abs().compareTo(accuracy) < 0) {
                System.out.println(i + " iterations neccessary.");
                break;
            }
            result = result.add(summand);
            preScaler = preScaler.multiply(preScalerFactor);
        }
        return result.setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * Shifted ein int-array nach links:
     * {1,2,3,4} wird zu {2,3,4,1}
     * 
     * @param array int[] array das geshifted wird
     */
    public static void shiftLeft(int[] array) {
        int temp = array[0];
        for (int i = 0; i < array.length - 1; i++) {
            array[i] = array[i + 1];
        }
        array[array.length - 1] = temp;
    }

    /**
     * Shifted ein int-array nach rechts:
     * {1,2,3,4} wird zu {4,1,2,3}
     * 
     * @param array int[] array das geshifted wird
     */
    public static void shiftRight(int[] array) {
        int temp = array[array.length - 1];
        for (int i = 1; i < array.length - 1; i++) {
            array[i] = array[i - 1];
        }
        array[0] = temp;
    }

    /**
     * Determines the maximum value in an int[] array
     * 
     * @param t int array of which the value is determined from
     * @return the maximum value in this int[] array
     */
    public static int max(int[] t) {
        if (t.length != 0) {
            int maximum = t[0];   // start with the first value
            for (int i = 1; i < t.length; i++) {
                if (t[i] > maximum) {
                    maximum = t[i];   // new maximum
                }
            }
            return maximum;
        } else {
            return 0;
        }

    }

    /**
     * Größter gemeinsamer Teiler zweier BigIntegers
     * @param a Zahl1
     * @param b Zahl2
     * @return Größter gemeinsamer Teiler
     */
    public static BigInteger ggT(BigInteger zahl1, BigInteger zahl2) throws JMathematicalException {
        // TODO: performance ist einfach nur sauschlecht im Moment. Verbessern!!!
        if ((zahl1.compareTo(BigInteger.ONE) == 0 || zahl2.compareTo(BigInteger.ONE) == 0) || zahl1.compareTo(zahl2) == 0) {
            return BigInteger.ONE;
        } else if (zahl1.compareTo(BigInteger.ZERO) == 0 && zahl2.compareTo(BigInteger.ZERO) == 0) {
            throw new JGCDException("ggT of 0 and 0 impossible.");
        } else if (zahl1.compareTo(BigInteger.ZERO) == 0) {
            return zahl2;
        } else if (zahl2.compareTo(BigInteger.ZERO) == 0) {
            return zahl1;
        } else {
            if (zahl1.compareTo(zahl2) > 0) { // zahl1 größer als zahl2
                //TODO: ist es möglich aus negativen zahlen ggt zu bilden
                while (zahl2.compareTo(BigInteger.ZERO) > 0) { // solange zahl2 nicht 0
                    if (zahl1.compareTo(zahl2) > 0) { // wenn zahl1 größer ist als zahl2
                        zahl1 = zahl1.subtract(zahl2); // dann zahl1 = zahl1 - zahl 2
                    } else {
                        zahl2 = zahl2.subtract(zahl1); // sonst zahl2 = zahl2 - zahl 1
                    }
                }
                return zahl1;
            } else {
                while (zahl1.compareTo(BigInteger.ZERO) > 0) {
                    if (zahl2.compareTo(zahl1) > 0) {
                        zahl2 = zahl2.subtract(zahl1);
                    } else {
                        zahl1 = zahl1.subtract(zahl2);
                    }
                }
                return zahl2;
            }
        }
    }

    /**
     * Größter gemeinsamer Teiler zweier BigIntegers
     *
     * fail bei 0, 0 usw...
     *
     * @param a Zahl1
     * @param b Zahl2
     * @return Größter gemeinsamer Teiler
     */
    @Deprecated
    public static int ggT(int zahl1, int zahl2) {
        while (zahl2 != 0) {
            if (zahl1 > zahl2) {
                zahl1 = zahl1 - zahl2;
            } else {
                zahl2 = zahl2 - zahl1;
            }
        }
        return zahl1;
    }

    public static boolean isPrime(BigInteger a) { // i.multiply(i)
        for (BigInteger i = BigInteger.valueOf(2); i.compareTo(a) < 0; i = i.add(BigInteger.ONE)) {
            boolean isteilbar = a.mod(i).compareTo(BigInteger.ZERO) == 0;
            if (isteilbar) {
                return false;
            }
        }
        return true;
    }

    public static BigInteger kgV(BigInteger a, BigInteger b) throws JMathematicalException {
        return a.multiply(b.divide(ggT(a, b)));     // Klammerung vermeidet Überlauf!
    }

    public static int minScale(BigDecimal a, BigDecimal b) {
        int firsttry = Math.min(a.scale(), b.scale());
        return firsttry >= 0 ? firsttry : 0;
    }

    /**
     * Liefert rest der scallerzahl sodass die eine 0 ist, die andere der nötige rest und der 10^x wert korrekt gesetzt wird
     * 
     * @param a
     * @param b
     * @return
     */
    public static int[] scaleRest(BigDecimal a, BigDecimal b) {
        int[] ret = new int[3];
        //0:a 1:b 2:10^X Faktor der kürzung
        if (a.scale() == b.scale()) { // beide gleiche scale
            ret[0] = 0; // beide auf 0 gekürzt
            ret[1] = 0;
            ret[2] = a.scale(); // nenner bekommt ausgleich
        } else if (a.scale() == 0 || b.scale() == 0) { // einer ist 0
            if (a.scale() > 0 || b.scale() > 0) { // der andere muss unter 1 sein -> scale ist positiv
                ret[0] = 0; // beide bekommen skale von 0
                ret[1] = 0;
                ret[2] = (a.scale() == 0) ? b.scale() : a.scale(); // nenner bekommt 10^scale von der nicht-null-scale Zahl
            } else {
                ret[0] = a.scale(); //beide behalten ihren scale
                ret[1] = b.scale(); //weil der nenner keine 10^- zahlen
                ret[2] = 0;
            }
        } else if (a.scale() < 0 || b.scale() < 0) { //beide behalten ihren scale weil der nenner kein 10^-x verträgt
            ret[0] = a.scale();
            ret[1] = b.scale();
            ret[2] = 0;
        } else if (a.scale() < b.scale()) { // a scale ist kleiner als b scale
            ret[0] = 0; // also wird a null
            ret[1] = b.scale() - a.scale(); // von b scale wird die a scale abgezogen
            ret[2] = a.scale(); // der nenner bekommt den abgezogenen wert zum ausgleich
        } else {
            ret[0] = a.scale() - b.scale(); // genau andersrum wie ein else if weiter oben
            ret[1] = 0;
            ret[2] = b.scale();
        }
        return ret;
    }
}
